﻿/*************************************************************************************
 * 工    具：  工具：HK_ProjectPro
 * 
 * 描    述：  本工具用于在 Project 面板中为特定的文件夹和文件显示自定义图标和标签；
 *            主要功能包括：
 *            1. 为文件夹和场景（Scene）添加颜色高亮、文本标签和详细描述。
 *            2. 支持通过检视面板（Inspector）对选中的文件夹或场景进行设置。
 *            3. 支持递归地将颜色和标签应用到子文件夹。
 *            4. 配置数据将保存到 ProjectSettings/HK_ProjectPro_Prefs.json 文件中。
 * 
 * 版    本：  V1.1
 * 作    者：  京产肠饭、NoEy
 * 
 * 创    建：  2023/11/20 V1.0
 * 
 * 更    新：  2025/06/17 V1.1
 *            1.功能新增：NoEy 整合 FolderTag 功能，重构代码 感谢FolderTag作者 liyingsong99分享
 *              参考 FolderTag https://github.com/liyingsong99/FolderTag
 *            
 * 链    接：  https://gitee.com/JingChanChangFan/hk_-unity-tools
*************************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

namespace HKTools
{
    /// <summary>
    /// 项目文件夹和场景标签管理工具
    /// 提供在Project面板中为文件夹和场景显示自定义图标、颜色和标签的功能 
    /// </summary>
    [InitializeOnLoad]
    internal class HK_ProjectPro
    {
        // 常量部分提取到顶部统一管理
        const string PrefsFile = nameof(HK_ProjectPro) + "_Prefs.json";
        const string PrefsPath = "ProjectSettings/" + PrefsFile;
        const int GradientWidth = 32;
        const int MaxTagLength = 50;

        // 静态只读字段
        static readonly HK_ProjectPro_Option<string> Opt_FolderTagPath = new HK_ProjectPro_Option<string>(nameof(HK_ProjectPro) + "_FolderTagPath", PrefsPath);
        static readonly HK_ProjectPro_Option<bool> Opt_EnableSceneTag = new HK_ProjectPro_Option<bool>(nameof(HK_ProjectPro) + "_EnableSceneTag", true);
        static readonly HK_ProjectPro_Option<bool> Opt_ShowGradient = new HK_ProjectPro_Option<bool>(nameof(HK_ProjectPro) + "_ShowGradient", true);
        static readonly HK_ProjectPro_Option<bool> Opt_EnableTagRendering = new HK_ProjectPro_Option<bool>(nameof(HK_ProjectPro) + "_EnableTagRendering", true);
        static readonly HK_ProjectPro_Option<Color> Opt_SubFoldersTint = new HK_ProjectPro_Option<Color>(nameof(HK_ProjectPro) + "_SubFoldersTint", new Color(0.7f, 0.7f, 0.7f, 0.7f));
        static readonly HK_ProjectPro_Option<Vector2> Opt_GradientScale = new HK_ProjectPro_Option<Vector2>(nameof(HK_ProjectPro) + "_GradientScale", new Vector2(0.7f, 1f));
        static readonly HashSet<string> IconFolderNames = new HashSet<string>
        {
            "_scripts", "_script", "scripts", "script",
            "_models", "models",
            "_prefabs","prefabs","_prefab","prefab",
            "resources",
            "streamingassets",
            "scenes","_scenes","scene","_scene",
            "materials","material","mats","mat","_materials","_material","_mats","_mat",
            "ui","_ui","uis","_uis",
            "hktools",
            "qframework","qframeworkdata"
        };
        static readonly Dictionary<string, Texture2D> IconCache = new Dictionary<string, Texture2D>();
        static readonly Dictionary<string, string> GuidToPathCache = new Dictionary<string, string>();

        // 图标名称的常量
        const string IconScriptsLight = "Icon_Scripts_Light";
        const string IconScriptsDark = "Icon_Scripts_Dark";
        const string IconPrefabsLight = "Icon_Prefabs_Light";
        const string IconPrefabsDark = "Icon_Prefabs_Dark";
        const string IconResourcesLight = "Icon_Resources_Light";
        const string IconResourcesDark = "Icon_Resources_Dark";
        const string IconStreamingAssetsLight = "Icon_StreamingAssets_Light";
        const string IconStreamingAssetsDark = "Icon_StreamingAssets_Dark";
        const string IconScenesLight = "Icon_Scenes_Light";
        const string IconScenesDark = "Icon_Scenes_Dark";
        const string IconMaterialsLight = "Icon_Materials_Light";
        const string IconMaterialsDark = "Icon_Materials_Dark";
        const string IconUILight = "Icon_UI_Light";
        const string IconUIDark = "Icon_UI_Dark";
        const string IconHKToolsLight = "Icon_HKTools_Light";
        const string IconHKToolsDark = "Icon_HKTools_Dark";
        const string IconQFrameworkLight = "Icon_QFramework_Light";
        const string IconQFrameworkDark = "Icon_QFramework_Dark";

        static Texture2D texGradient;
        internal static Color FoldersDescColor { get; private set; }
        static Color foldersDescTint = Color.white;
        static Dictionary<string, FolderData> dicFoldersData;
        static List<FolderData> listFoldersData;
        static ReorderableList scenePreviewList;
        static ReorderableList folderPreviewList;
        internal static bool IsActive { get; private set; } = true;

        /// <summary>
        /// 用于JSON序列化的包装类
        /// </summary>
        [Serializable]
        class JsonWrapper
        {
            /// <summary>
            /// 标签颜色
            /// </summary>
            public Color LabelColor;

            /// <summary>
            /// 文件夹数据字典
            /// </summary>
            public DictionaryData<string, FolderData> FoldersData;

            /// <summary>
            /// 用于JSON序列化的字典数据类
            /// </summary>
            [Serializable]
            public class DictionaryData<TKey, TValue>
            {
                /// <summary>
                /// 键列表
                /// </summary>
                public List<TKey> Keys;

                /// <summary>
                /// 值列表
                /// </summary>
                public List<TValue> Values;

                /// <summary>
                /// 枚举所有键值对
                /// </summary>
                /// <returns>键值对枚举器</returns>
                public IEnumerable<KeyValuePair<TKey, TValue>> Enumerate()
                {
                    if (Keys == null || Values == null)
                        yield break;

                    for (var n = 0; n < Keys.Count; n++)
                        yield return new KeyValuePair<TKey, TValue>(Keys[n], Values[n]);
                }

                /// <summary>
                /// 构造函数
                /// </summary>
                /// <param name="data">键值对集合</param>
                public DictionaryData(IEnumerable<KeyValuePair<TKey, TValue>> data)
                {
                    var pairs = data as KeyValuePair<TKey, TValue>[] ?? data.ToArray();
                    Keys = pairs.Select(p => p.Key).ToList();
                    Values = pairs.Select(p => p.Value).ToList();
                }
            }
        }

        /// <summary>
        /// 文件夹数据类
        /// </summary>
        [Serializable]
        class FolderData
        {
            /// <summary>
            /// 资源GUID
            /// </summary>
            public string Guid;

            /// <summary>
            /// 是否为场景
            /// </summary>
            public bool IsScene;

            /// <summary>
            /// 颜色
            /// </summary>
            public Color Color;

            /// <summary>
            /// 是否递归应用到子文件夹
            /// </summary>
            public bool Recursive;

            /// <summary>
            /// 标签文本
            /// </summary>
            public string Tag = "folder tag";

            /// <summary>
            /// 描述文本
            /// </summary>
            public string Description = "";

            /// <summary>
            /// 标签颜色
            /// </summary>
            public Color TagColor = Color.white;
        }

        /// <summary>
        /// 改变插件的激活状态
        /// </summary>
        /// <param name="isActive">是否激活</param>
        internal static void ChangeState(bool isActive)
        {
            IsActive = isActive;

            if (isActive)
            {
                EditorApplication.projectWindowItemOnGUI += OnProjectWindowItemOnGUI;
            }
            else
            {
                EditorApplication.projectWindowItemOnGUI -= OnProjectWindowItemOnGUI;
            }

            EditorApplication.RepaintProjectWindow();

            // 刷新Inspector窗口，使自定义Inspector界面更新
            if (Selection.activeObject != null)
            {
                EditorUtility.SetDirty(Selection.activeObject);
            }
        }

        static HK_ProjectPro()
        {
            LoadPreferences();
            UpdateGradient();
            PreloadIcons();
        }

        static void PreloadIcons()
        {
            foreach (var folderName in IconFolderNames)
            {
                GetFolderIcon(folderName);
            }
        }

        static void LoadPreferences()
        {
            if (File.Exists(Opt_FolderTagPath.Value))
            {
                using var file = File.OpenText(Opt_FolderTagPath.Value);
                try
                {
                    var data = JsonUtility.FromJson<JsonWrapper>(file.ReadToEnd());

                    listFoldersData = data.FoldersData
                        .Enumerate()
                        .Select(n => n.Value)
                        .ToList();

                    FoldersDescColor = data.LabelColor;
                }
                catch
                {
                    listFoldersData = new List<FolderData>();
                    FoldersDescColor = foldersDescTint;
                }
            }
            else
            {
                listFoldersData = new List<FolderData>();
                FoldersDescColor = foldersDescTint;
            }

            // 过滤掉 Guid 为 null 的项，避免 ToDictionary 方法抛出异常
            dicFoldersData = listFoldersData
                .Where(n => n.Guid != null)
                .ToDictionary(n => n.Guid, n => n);
        }

        /// <summary>
        /// 添加文件夹数据到列表
        /// </summary>
        /// <param name="folderData">文件夹数据</param>
        static void AddFoldersList(FolderData folderData)
        {
            listFoldersData.Add(folderData);
            dicFoldersData.Add(folderData.Guid, folderData);
        }

        /// <summary>
        /// 创建文件夹数据
        /// </summary>
        /// <param name="isScene">是否为场景</param>
        /// <returns>新的文件夹数据</returns>
        static FolderData CreateFolderData(bool isScene = false)
        {
            return new FolderData
            {
                IsScene = isScene,
                Color = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value, 0.3f),
                Recursive = false,
                TagColor = Color.white
            };
        }

        /// <summary>
        /// 获取文件夹数据
        /// </summary>
        /// <param name="guid">资源GUID</param>
        /// <param name="path">资源路径</param>
        /// <param name="subFolder">输出参数，指示是否为子文件夹</param>
        /// <returns>文件夹数据，如果不存在则返回null</returns>
        static FolderData GetFolderData(string guid, string path, out bool subFolder)
        {
            subFolder = false;
            if (dicFoldersData.TryGetValue(guid, out var data))
            {
                return data;
            }

            foreach (var pair in dicFoldersData)
            {
                if (!pair.Value.Recursive) continue;

                var folderPath = GetPathFromGuid(pair.Key);
                if (string.IsNullOrEmpty(folderPath)) continue;

                if (path.StartsWith(folderPath + "/"))
                {
                    subFolder = true;
                    return pair.Value;
                }
            }

            return null;
        }

        static string GetPathFromGuid(string guid)
        {
            if (!GuidToPathCache.TryGetValue(guid, out var path))
            {
                path = AssetDatabase.GUIDToAssetPath(guid);
                GuidToPathCache[guid] = path;
            }
            return path;
        }

        /// <summary>
        /// 保存项目偏好设置
        /// </summary>
        static void SaveProjectPrefs()
        {
            var wrapper = new JsonWrapper
            {
                FoldersData = new JsonWrapper.DictionaryData<string, FolderData>(dicFoldersData),
                LabelColor = FoldersDescColor
            };
            File.WriteAllText(Opt_FolderTagPath.Value, JsonUtility.ToJson(wrapper, true));
        }

        /// <summary>
        /// 添加当前选中的文件夹或场景标签
        /// </summary>
        internal static void AddSelectedAssetTag()
        {
            if (Selection.assetGUIDs.Length != 1)
            {
                ShowDialog("提示", "请选择单个文件夹或场景", false);
                return;
            }

            var guid = Selection.assetGUIDs[0];
            var path = GetPathFromGuid(guid);

            var isScene = HK_ProjectPro_Helper.IsValidScene(path);
            var isFolder = HK_ProjectPro_Helper.IsValidFolder(path);

            if (!isFolder && !isScene)
            {
                ShowDialog("提示", "请选择文件夹或场景文件", false);
                return;
            }

            if (isScene && !Opt_EnableSceneTag.Value)
            {
                ShowDialog("提示", "场景标签功能已禁用", false);
                return;
            }

            // 检查是否已存在
            if (dicFoldersData.ContainsKey(guid))
            {
                ShowDialog("提示", "该资源已添加标签", false);
                return;
            }

            // 创建并添加新标签
            var folderData = CreateFolderData(isScene);
            folderData.Guid = guid;
            folderData.Tag = Path.GetFileNameWithoutExtension(path);

            AddFoldersList(folderData);
            SaveProjectPrefs();
            EditorApplication.RepaintProjectWindow();

            // 重新创建列表
            GetFoldersList(true, isScene);

            ShowDialog("成功", $"已为 {Path.GetFileNameWithoutExtension(path)} 添加标签", false);
        }

        /// <summary>
        /// 显示编辑器对话框
        /// </summary>
        /// <param name="title">对话框标题</param>
        /// <param name="message">对话框消息</param>
        /// <param name="hasCancelButton">是否显示取消按钮</param>
        /// <returns>如果显示取消按钮，返回用户点击确定或取消的结果；否则返回 true</returns>
        static bool ShowDialog(string title, string message, bool hasCancelButton = false)
        {
            return hasCancelButton
                ? EditorUtility.DisplayDialog(title, message, "确定", "取消")
                : EditorUtility.DisplayDialog(title, message, "确定");
        }

        /// <summary>
        /// 项目窗口项GUI绘制回调
        /// </summary>
        /// <param name="guid">资源GUID</param>
        /// <param name="selectionRect">选择区域矩形</param>
        static void OnProjectWindowItemOnGUI(string guid, Rect selectionRect)
        {
            DrawFolderTag(guid, selectionRect);
            DrawFolderIcon(guid, selectionRect);
        }

        /// <summary>
        /// 更新渐变纹理
        /// </summary>
        static void UpdateGradient()
        {
            texGradient = new Texture2D(GradientWidth, 1) { wrapMode = TextureWrapMode.Clamp };
            var range = Opt_GradientScale.Value;

            for (var x = 0; x < GradientWidth; x++)
            {
                var xScale = x / (GradientWidth - 1f);
                float alpha;
                if (range == new Vector2(0, 1))
                {
                    alpha = 1f;
                }
                else
                {
                    if (xScale >= range.x && xScale <= range.y)
                    {
                        alpha = 1f;
                    }
                    else
                    {
                        var distance = xScale < range.x ? range.x - xScale : xScale - range.y;
                        alpha = Mathf.Clamp01(1f - distance * 1.5f);
                    }
                }
                texGradient.SetPixel(x, 0, new Color(1, 1, 1, alpha));
            }
            texGradient.Apply();
        }

        /// <summary>
        /// 获取文件夹列表控件
        /// </summary>
        /// <param name="forceNew">是否强制创建新列表</param>
        /// <param name="isScene">是否为场景列表</param>
        /// <returns>可重排序列表控件</returns>
        static ReorderableList GetFoldersList(bool forceNew = false, bool isScene = false)
        {
            var list = isScene ? scenePreviewList : folderPreviewList;
            if (list != null && !forceNew) return list;

            var displayList = listFoldersData.Where(n => n.IsScene == isScene).ToList();
            list = new ReorderableList(displayList, typeof(FolderData), true, true, false, true);
            list.drawHeaderCallback = rect => EditorGUI.LabelField(rect, $"{(isScene ? "场景" : "文件夹")}标签");
            list.drawElementCallback = (rect, index, isActive, isFocused) => DrawFolderListItem(rect, index, isActive, isFocused, displayList);
            list.onSelectCallback = l => EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(GetPathFromGuid(displayList[l.index].Guid)));
            list.onRemoveCallback = l => RemoveFolderListItem(l, isScene, displayList);

            if (isScene)
                scenePreviewList = list;
            else
                folderPreviewList = list;

            return list;
        }

        static void DrawFolderListItem(Rect rect, int index, bool isActive, bool isFocused, List<FolderData> displayList)
        {
            var item = displayList[index];
            var path = GetPathFromGuid(item.Guid);

            var r = rect;
            r.width = 50;
            r.height = 16;
            r.y += 2;

            var color = EditorGUI.ColorField(r, item.Color);
            if (color != item.Color)
            {
                item.Color = color;
                SaveProjectPrefs();
            }

            r.xMin = r.xMax + 5;
            r.width = rect.width - 320;

            var icon = AssetDatabase.GetCachedIcon(path);
            GUI.Label(r, new GUIContent(Path.GetFileNameWithoutExtension(path), icon));

            r.xMin = r.xMax + 5;
            r.width = 80;
            var strTag = EditorGUI.TextField(r, item.Tag);
            item.Tag = strTag.Length > MaxTagLength ? strTag[..MaxTagLength] : strTag;
            if (strTag != item.Tag)
            {
                SaveProjectPrefs();
            }

            r.xMin = r.xMax + 5;
            r.width = 50;
            var tagColor = EditorGUI.ColorField(r, item.TagColor);
            if (tagColor != item.TagColor)
            {
                item.TagColor = tagColor;
                SaveProjectPrefs();
            }

            r.xMin = r.xMax + 5;
            r.xMax = rect.xMax;
            var recursive = EditorGUI.ToggleLeft(r, "递归显示", item.Recursive);
            if (recursive != item.Recursive)
            {
                item.Recursive = recursive;
                SaveProjectPrefs();
            }
        }

        static void RemoveFolderListItem(ReorderableList list, bool isScene, List<FolderData> displayList)
        {
            if (list.index >= 0 && list.index < displayList.Count)
            {
                var item = displayList[list.index];
                if (ShowDialog("删除标签", $"确定要删除 {GetPathFromGuid(item.Guid)} 的标签吗？", true))
                {
                    listFoldersData.Remove(item);
                    dicFoldersData.Remove(item.Guid);
                    SaveProjectPrefs();
                    EditorApplication.RepaintProjectWindow();

                    // 重新创建列表
                    if (isScene)
                        scenePreviewList = null;
                    else
                        folderPreviewList = null;

                    GetFoldersList(true, isScene);
                }
            }
        }

        /// <summary>
        /// 绘制文件夹标签
        /// </summary>
        /// <param name="guid">资源GUID</param>
        /// <param name="rect">绘制区域</param>
        static void DrawFolderTag(string guid, Rect rect)
        {
            var enableTagRendering = Opt_EnableTagRendering.Value;
            var enableSceneTag = Opt_EnableSceneTag.Value;
            var showGradient = Opt_ShowGradient.Value;

            if (!enableTagRendering)
                return;

            if (rect.width < rect.height)
                return;

            var path = GetPathFromGuid(guid);
            if (!HK_ProjectPro_Helper.IsValidFolder(path) && !HK_ProjectPro_Helper.IsValidScene(path))
                return;

            if (!enableSceneTag && HK_ProjectPro_Helper.IsValidScene(path))
                return;

            var data = GetFolderData(guid, path, out var isSubFolder);
            if (data == null)
                return;

            bool curIsTreeView = (rect.x - 16) % 14 == 0;
            if (!curIsTreeView)
                rect.xMin += 3;

            if (showGradient)
            {
                GUI.color = isSubFolder ? data.Color * Opt_SubFoldersTint.Value : data.Color;
                GUI.DrawTexture(rect, Gradient, ScaleMode.ScaleAndCrop);
            }

            if (!string.IsNullOrEmpty(data.Tag))
            {
                GUI.color = data.TagColor;
                var style = new GUIStyle(GUI.skin.label)
                {
                    normal = { textColor = data.TagColor },
                    hover = { textColor = data.TagColor }
                };
                var englishSize = style.CalcSize(new GUIContent(data.Tag));

                var x = rect.xMax - englishSize.x > 0 ? (int)(rect.xMax - englishSize.x) : 0;
                GUI.Label(new Rect(x, rect.y - 1, englishSize.x, englishSize.y), data.Tag, style);
            }

            if (!string.IsNullOrEmpty(data.Description))
            {
                const float indicatorSize = 4f;
                var indicatorRect = new Rect(
                    rect.x + 2,
                    rect.y + rect.height - indicatorSize - 2,
                    indicatorSize,
                    indicatorSize
                );

                GUI.color = new Color(0.2f, 0.6f, 1f, 0.8f);
                GUI.DrawTexture(indicatorRect, EditorGUIUtility.whiteTexture);
                GUI.color = Color.white;
            }

            GUI.color = Color.white;
        }

        /// <summary>
        /// 为项目窗口中的每个可见的列表项绘制图标
        /// </summary>
        /// <param name="guid">资源GUID</param>
        /// <param name="rect">绘制区域</param>
        static void DrawFolderIcon(string guid, Rect rect)
        {
            var path = GetPathFromGuid(guid);
            if (AssetDatabase.IsValidFolder(path))
            {
                var folderName = Path.GetFileName(path).ToLower();
                if (!IconFolderNames.Contains(folderName))
                    return;

                var icon = GetFolderIcon(folderName);
                if (icon != null)
                {
                    var scale = Mathf.Min(rect.width / icon.width, rect.height / icon.height);
                    var iconRect = new Rect(rect.x, rect.y, icon.width * scale, icon.height * scale);
                    GUI.DrawTexture(iconRect, icon);
                }
            }
        }

        /// <summary>
        /// 获取文件夹图标
        /// </summary>
        /// <param name="folderName">文件夹名称</param>
        /// <returns>图标纹理</returns>
        static Texture2D GetFolderIcon(string folderName)
        {
            if (IconCache.TryGetValue(folderName, out var icon))
            {
                return icon;
            }

            var iconName = GetIconName(folderName);

            if (!string.IsNullOrEmpty(iconName))
            {
                icon = Resources.Load<Texture2D>(iconName);
                if (icon != null)
                {
                    IconCache[folderName] = icon;
                }
            }

            return icon;
        }

        static string GetIconName(string folderName)
        {
            var isProSkin = EditorGUIUtility.isProSkin;
            return folderName switch
            {
                "hktools" when isProSkin => IconHKToolsLight,
                "hktools" => IconHKToolsDark,
                "qframework" or "qframeworkdata" when isProSkin => IconQFrameworkLight,
                "qframework" or "qframeworkdata" => IconQFrameworkDark,
                "_script" or "_scripts" or "script" or "scripts" when isProSkin => IconScriptsLight,
                "_script" or "_scripts" or "script" or "scripts" => IconScriptsDark,
                "_prefabs" or "prefabs" or "_prefab" or "prefab" when isProSkin => IconPrefabsLight,
                "_prefabs" or "prefabs" or "_prefab" or "prefab" => IconPrefabsDark,
                "resources" when isProSkin => IconResourcesLight,
                "resources" => IconResourcesDark,
                "streamingassets" when isProSkin => IconStreamingAssetsLight,
                "streamingassets" => IconStreamingAssetsDark,
                "_scenes" or "scenes" or "_scene" or "scene" when isProSkin => IconScenesLight,
                "_scenes" or "scenes" or "_scene" or "scene" => IconScenesDark,
                "materials" or "material" or "mats" or "mat" or "_materials" or "_material" or "_mats" or "_mat" when isProSkin => IconMaterialsLight,
                "materials" or "material" or "mats" or "mat" or "_materials" or "_material" or "_mats" or "_mat" => IconMaterialsDark,
                "ui" or "_ui" or "uis" or "_uis" when isProSkin => IconUILight,
                "ui" or "_ui" or "uis" or "_uis" => IconUIDark,
                _ => ""
            };
        }

        // 新增基类
        internal abstract class HK_ProjectPro_BaseInspector : Editor
        {
            /// <summary>
            /// 是否显示预览
            /// </summary>
            static bool isShowPreview;

            /// <summary>
            /// 绘制检视器界面的公共逻辑
            /// </summary>
            /// <param name="isScene">是否为场景</param>
            protected void DrawCommonInspectorGUI(bool isScene)
            {
                if (!HK_ProjectPro.IsActive)
                    return;

                if (Selection.assetGUIDs.Length != 1)
                    return;

                var guid = Selection.assetGUIDs[0];
                var path = HK_ProjectPro.GetPathFromGuid(guid);

                if (isScene && !HK_ProjectPro.Opt_EnableSceneTag.Value)
                    return;

                var isValid = isScene ? HK_ProjectPro_Helper.IsValidScene(path) : HK_ProjectPro_Helper.IsValidFolder(path);
                if (!isValid)
                    return;

                var folderData = HK_ProjectPro.GetFolderData(guid, path, out _);

                GUI.enabled = true;
                var create = folderData == null;

                if (create)
                {
                    EditorGUILayout.HelpBox($"该 {(isScene ? "场景" : "文件夹")} 尚未添加标签", MessageType.Info);

                    if (GUILayout.Button("添加标签"))
                    {
                        HK_ProjectPro.AddSelectedAssetTag();
                        return;
                    }

                    GUI.enabled = false;
                    return;
                }

                EditorGUI.BeginChangeCheck();

                EditorGUILayout.LabelField("颜色标记");
                folderData.Color = EditorGUILayout.ColorField(folderData.Color);

                EditorGUILayout.LabelField("标签");
                var strTag = EditorGUILayout.TextField(folderData.Tag, EditorStyles.textField);
                folderData.Tag = strTag.Length > MaxTagLength ? strTag[..MaxTagLength] : strTag;

                EditorGUILayout.LabelField("标签颜色");
                folderData.TagColor = EditorGUILayout.ColorField(folderData.TagColor);

                GUILayout.Space(5);

                EditorGUILayout.LabelField("描述");
                folderData.Description = EditorGUILayout.TextArea(folderData.Description, EditorStyles.textArea, GUILayout.MinHeight(300));

                if (EditorGUI.EndChangeCheck())
                {
                    if (create) HK_ProjectPro.AddFoldersList(folderData);
                    EditorApplication.RepaintProjectWindow();
                    HK_ProjectPro.SaveProjectPrefs();
                }

                GUILayout.Space(10);

                EditorGUILayout.BeginHorizontal();
                if (GUILayout.Button("删除此标签"))
                {
                    if (HK_ProjectPro.ShowDialog("删除标签", "确定要删除此标签吗？", true))
                    {
                        HK_ProjectPro.listFoldersData.Remove(folderData);
                        HK_ProjectPro.dicFoldersData.Remove(guid);
                        HK_ProjectPro.SaveProjectPrefs();
                        EditorApplication.RepaintProjectWindow();
                    }
                }
                EditorGUILayout.EndHorizontal();

                GUILayout.Space(10);

                var title = isShowPreview ? " ∧ 隐藏标签预览" : " ∨ 显示标签预览";
                isShowPreview = EditorGUILayout.BeginFoldoutHeaderGroup(isShowPreview, title);
                if (isShowPreview)
                {
                    HK_ProjectPro.GetFoldersList(false, isScene).DoLayoutList();
                }

                GUI.enabled = false;
            }
        }

        /// <summary>
        /// 文件夹（Folder）检视器自定义界面
        /// </summary>
        [CustomEditor(typeof(DefaultAsset))]
        internal class HK_ProjectPro_FolderInspector : HK_ProjectPro_BaseInspector
        {
            /// <summary>
            /// 绘制检视器界面
            /// </summary>
            public override void OnInspectorGUI()
            {
                DrawCommonInspectorGUI(false);
            }
        }

        /// <summary>
        /// 场景（Scene）检视器自定义界面
        /// </summary>
        [CustomEditor(typeof(SceneAsset))]
        internal class HK_ProjectPro_SceneInspector : HK_ProjectPro_BaseInspector
        {
            /// <summary>
            /// 绘制检视器界面
            /// </summary>
            public override void OnInspectorGUI()
            {
                DrawCommonInspectorGUI(true);
            }
        }

        /// <summary>
        /// 配置选项类，用于存储和读取编辑器偏好设置
        /// </summary>
        /// <typeparam name="T">选项值的类型</typeparam>
        class HK_ProjectPro_Option<T>
        {
            readonly string key;
            T val;
            bool isLoaded;

            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="key">偏好设置键名</param>
            /// <param name="val">默认值</param>
            public HK_ProjectPro_Option(string key, T val)
            {
                this.key = key;
                this.val = val;
            }

            /// <summary>
            /// 选项值
            /// </summary>
            public T Value
            {
                get
                {
                    if (!isLoaded)
                    {
                        Get();
                        isLoaded = true;
                    }

                    return val;
                }
                set => Set(value);
            }

            /// <summary>
            /// 从EditorPrefs获取值
            /// </summary>
            void Get()
            {
                if (!EditorPrefs.HasKey(key)) return;
                try
                {
                    var type = typeof(T);

                    if (type == typeof(bool))
                        val = (T)(object)EditorPrefs.GetBool(key);
                    else if (type == typeof(int))
                        val = (T)(object)EditorPrefs.GetInt(key);
                    else if (type == typeof(float))
                        val = (T)(object)EditorPrefs.GetFloat(key);
                    else if (type == typeof(string))
                        val = (T)(object)EditorPrefs.GetString(key);
                    else
                        val = JsonUtility.FromJson<T>(EditorPrefs.GetString(key));
                }
                catch
                {
                }
            }

            /// <summary>
            /// 设置值到EditorPrefs
            /// </summary>
            /// <param name="val">要设置的值</param>
            void Set(T val)
            {
                var type = typeof(T);
                this.val = val;

                if (type == typeof(bool))
                    EditorPrefs.SetBool(key, this.val.Equals(true));
                else if (type == typeof(int))
                    EditorPrefs.SetInt(key, (int)(object)this.val);
                else if (type == typeof(string))
                    EditorPrefs.SetString(key, (string)(object)this.val);
                else if (type == typeof(float))
                    EditorPrefs.SetFloat(key, (float)(object)this.val);
                else
                    EditorPrefs.SetString(key, JsonUtility.ToJson(val));
            }
        }

        /// <summary>
        /// 工具助手类，提供常用的辅助方法
        /// </summary>
        internal static class HK_ProjectPro_Helper
        {
            /// <summary>
            /// 检查路径是否为有效的文件夹
            /// </summary>
            /// <param name="path">资源路径</param>
            /// <returns>如果是有效的文件夹则返回true</returns>
            public static bool IsValidFolder(string path)
            {
                return AssetDatabase.IsValidFolder(path) && !path.Equals("Assets");
            }

            /// <summary>
            /// 检查路径是否为有效的场景
            /// </summary>
            /// <param name="path">资源路径</param>
            /// <returns>如果是有效的场景则返回true</returns>
            public static bool IsValidScene(string path)
            {
                return path.EndsWith(".unity");
            }
        }

        /// <summary>
        /// 获取渐变纹理
        /// </summary>
        internal static Texture Gradient
        {
            get
            {
                if (texGradient == null)
                    UpdateGradient();

                return texGradient;
            }
        }
    }
}